'use strict';

var chalk = require('chalk');
var exec = require('child_process').exec;
var fsx = require('fs-extra');
var globby = require('globby');
var path = require('path');

var writeSpec = require('./spec');
const tmp = require('tmp-promise')
const { format } = require("util");

var logger;

/**
 * Creates the folder structure needed to create
 * the RPM package.
 *
 * @param  {String} tmpDir the path where the folder structure will reside
 */
function setupTempDir (tmpDir) {
  var rpmStructure = ['BUILD', 'BUILDROOT', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS'];

  // If the tmpDir exists (probably from previous build), delete it first
  if (fsx.existsSync(tmpDir)) {
    logger(chalk.cyan('Removing old temporary directory.'));
    fsx.removeSync(tmpDir);
  }

  // Create RPM folder structure
  logger(chalk.cyan('Creating RPM directory structure at:'), tmpDir);
  rpmStructure.map((dirName) => {
    fsx.ensureDir(path.join(tmpDir, dirName), '0755');
  });
}

/**
 * Expand the patterns/files (specified by the user)
 * that should be ignored when building the RPM package.
 *
 * @param  {Array} excludeFiles patterns/files to ignore
 * @return {Array}              expanded list of files to ignore
 */
function retrieveFilesToExclude (excludeFiles) {
  return globby.sync(excludeFiles).map(function (file) {
    return path.normalize(file);
  });
}

function checkDirective (directive) {
  if (typeof directive === 'undefined') {
    return true;
  }
  return directive.match(/^(?:doc|config|attr|verify|docdir|dir)/);
}

/**
 * 1. Normalize and expand the patterns/files (specified by the user)
 *    that should be included in the RPM package.
 * 2. Copies the files to the respective destination.
 *
 * @param  {Array}  files         patterns/files to include
 * @param  {Array}  excludeFiles  expanded list of files to ignore
 * @param  {String} buildRoot     where all files should be copied into
 * @return {Array}                list of files to include in the RPM
 */
async function prepareFiles (files, excludeFiles, buildRoot) {
  var _files = [];
  var filesToExclude = retrieveFilesToExclude(excludeFiles);
  for (let file of files) {

    if (!file.hasOwnProperty('src') || !file.hasOwnProperty('dest')) {
      throw new Error('All files/folders must have source (src) and destination (dest) set');
    }

    file.cwd = (file.cwd || '.') + '/';

    var actualSrc = globby.sync(path.join(file.cwd, file.src));

    fsx.ensureDir(path.join(buildRoot, file.dest));

    for (let srcFile of actualSrc) {

      // Check whether to ignore this file
      if (filesToExclude.indexOf(srcFile) > -1) {
        return;
      }

      // files/folders should be copied
      // taking into account the cwd
      // so the destination should be
      // relative to the cwd
      var copyTarget = path.normalize(srcFile).replace(path.normalize(file.cwd), '');
      var dest = path.join(file.dest, copyTarget);

      if (checkDirective(file.directive)) {
        _files.push({ path: dest, directive: file.directive });
      } else {
        throw new Error('Invalid file directive informed: ' + file.directive);
      }

      await fsx.copy(srcFile, path.join(buildRoot, dest));
    }
  }

  return _files;
}

/**
 * Runs the rpmbuild tool in a child process.
 *
 * @param  {String}   buildRoot  where all included files reside
 * @param  {String}   specFile   path to the file from which the RPM package will be created
 * @param  {String}   rpmDest    where the .rpm file should be copied to
 * @param  {Object}   execOpts
 */
function buildRpm (buildRoot, specFile, rpmDest, execOpts) {
  // Build the RPM package.
  var cmd = [
    'rpmbuild',
    '-bb',
    '--buildroot',
    buildRoot,
    specFile
  ].join(' ');

  logger(chalk.cyan('Executing:'), cmd);

  execOpts = execOpts || {};

  return new Promise((resolve, reject) => {
    exec(cmd, execOpts, async function (err, stdout) {

      if (err) {
        return reject(err);
      }

      if (stdout) {
        var rpm = stdout.match(/(\/.+\..+\.rpm)/);

        if (rpm && rpm.length > 0) {
          var rpmDestination = rpm[0];

          if (rpmDest) {
            rpmDestination = path.join(rpmDest, path.basename(rpmDestination));
            logger(chalk.cyan('Copying RPM package to:'), rpmDestination);
            await fsx.copy(rpm[0], rpmDestination);
          }

          return resolve(rpmDestination);
        }
      }
    });
  })
}

const archMap = {
  arm64: 'amd64'
}

async function rpm (config) {
  if (!config || typeof config !== 'object') {
    throw new TypeError('config object is missing');
  }

  let options = {
    name: config.name,
    version: config.pkg.version,
    release: '1',
    buildArch: 'noarch',
    files: [
      { cwd: config.src, src: '*', dest: `/opt/apps/${config.name}/files/` }
    ],
    rpmDest: config.dest,
    prepScript: ["echo prepScript"],
    buildScript: ["echo 构建"],
    installScript: ["echo 安装"],
    checkScript: ["echo 检查"],
    cleanScript: ["echo 清理"],
    verifyScript: ["echo verifyScript"],
    preInstallScript: [],
    postInstallScript: [],
    preUninstallScript: [],
    postUninstallScript: [],
  }
  const path = require("path")
  if (config.scripts?.preinst) {
    const src=config.scripts?.preinst
    let pathArg = path.parse(src)
    const baseDest = options.files[0].dest
    const dest = path.resolve(baseDest, pathArg.name)
    options.files.push({ src: src, dest: baseDest })
    options.preInstallScript.push(`sh ${dest}`)
  }
  if (config.scripts?.postinst) {
    const src=config.scripts?.postinst
    let pathArg = path.parse(src)
    const baseDest = options.files[0].dest
    const dest = path.resolve(baseDest, pathArg.name)
    options.files.push({ src: src, dest: baseDest })
    options.postInstallScript.push(`sh ${dest}`)
  }
  options.postInstallScript.push(`echo ${config.name}安装结束`)
  if (config.scripts?.prerm) {
    const src=config.scripts?.prerm
    let pathArg = path.parse(src)
    const baseDest = options.files[0].dest
    const dest = path.resolve(baseDest, pathArg.name)
    options.files.push({ src: src, dest: baseDest })
    options.preUninstallScript.push(`sh ${dest}`)
  }
  if (config.scripts?.postrm) {
    const src=config.scripts?.postrm
    let pathArg = path.parse(src)
    const baseDest = options.files[0].dest
    const dest = path.resolve(baseDest, pathArg.name)
    options.files.push({ src: src, dest: baseDest })
    options.postUninstallScript.push(`sh ${dest}`)
  }
  options.postUninstallScript.push(`rm -rf /opt/apps/${config.name}`)
  options.postUninstallScript.push(`echo ${config.name}卸载完成`)
  console.log(JSON.stringify(options, null, 2))
  const dir = await tmp.dir({ prefix: 'rpm-builder-', unsafeCleanup: true })
  var defaults = {
    name: 'no-name',
    summary: 'No summary',
    description: 'No description',
    version: '0.0.0',
    release: '1',
    epoch: '',
    license: 'MIT',
    vendor: 'Vendor',
    group: 'Development/Tools',
    buildArch: 'noarch',
    tempDir: dir.path,
    files: [],
    excludeFiles: [],
    rpmDest: process.cwd(),
    keepTemp: false,
    verbose: true,
    execOpts: {}
  };

  options = {
    ...defaults,
    ...options
  };

  logger = function () {
    var msg = format.apply(null, arguments);
    if (!process.stdout.isTTY) {
      msg = chalk.stripColor(msg);
    }
    process.stdout.write(msg + '\n');
  };

  var tmpDir = path.resolve(options.tempDir);
  var buildRoot = path.join(tmpDir, '/BUILDROOT/');
  var files = [];

  setupTempDir(tmpDir);

  try {
    files = await prepareFiles(options.files, options.excludeFiles, buildRoot);
  } catch (ex) {
    throw ex
  }

  // Write spec file
  var specFile = await writeSpec(files, options);
  logger(chalk.cyan('SPEC file created:'), specFile);

  let rpm = await buildRpm(buildRoot, specFile, options.rpmDest, options.execOpts);
  // Remove temp folder
  if (!options.keepTemp) {
    logger(chalk.cyan('Removing RPM directory structure at:'), tmpDir);
    fsx.removeSync(tmpDir);
  }

  return rpm
}

module.exports = rpm;
// rpmbuild -bb --buildroot /mnt/e/workspace/aaaa/builder-rpm/tmp-oKTj4nn7P/BUILDROOT/ tmp-oKTj4nn7P/SPECS/kjcgzhpt-server-1.0.0-1.noarch.spec
